package editortrees;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.NoSuchElementException;

import javax.swing.JComponent;
import javax.swing.JFrame;

import editortrees.Node.Code;

/**
 * A wrapper class for binary trees that can display the wrapped tree in a window.
 * 
 * @author Curt Clifton. Created Jan 24, 2008.
 */
public class DisplayableBinaryTree {

	private int width;

	private int height;

	private EditTree tree;

	/**
	 * Constructs a new displayable binary tree, set to default to the given window size for display.
	 * 
	 * @param tree
	 * @param windowWidth
	 *            in pixels
	 * @param windowHeight
	 *            in pixels
	 */
	public DisplayableBinaryTree(EditTree tree, int windowWidth, int windowHeight) {
		this.width = windowWidth;
		this.height = windowHeight;
		this.tree = tree;
	}

	/**
	 * Creates a new window, using the current size information stored in this, and renders the current state of the
	 * tree wrapped by this.
	 */
	public void display() {
		JFrame frame = new JFrame();
		frame.setTitle("Displayable");
		// frame.setSize(this.width, this.height);
		frame.add(new Draw(this.width, this.height, this.tree));
		frame.pack();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}

	/**
	 * Sets the default size for the next window displayed.
	 * 
	 * @param windowWidth
	 *            in pixels
	 * @param windowHeight
	 *            in pixels
	 */
	public void setSize(int windowWidth, int windowHeight) {
		this.width = windowWidth;
		this.height = windowHeight;
	}
	//
	// public static void main(String[] args) {
	// // DisplayableBinaryTree t = new DisplayableBinaryTree(
	// // BuildTree.preOrderBuild("ABCDEFG", "2022000"), 200, 200);
	// // DisplayableBinaryTree t = new DisplayableBinaryTree(
	// // BuildTree.preOrderBuild("ECBADGFH", "22L00200"), 200, 200);
	// BinaryTree tree = BuildTree.preOrderBuild("ABCDEFGHIJK", "2RL200RLLR0");
	// DisplayableBinaryTree t2 = new DisplayableBinaryTree(tree,1200, 600);
	// tree.printPostOrder();
	// t2.display();
	//
	// }

}

/**
 * Draws a tree in a window
 */
class Draw extends JComponent {

	private static final long serialVersionUID = 8742470470682848151L;

	private int width;

	private int height;

	private EditTree tree;

	/**
	 * Initializes the drawer
	 *
	 * @param width
	 * @param height
	 * @param tree
	 */
	public Draw(int width, int height, EditTree tree) {
		this.width = width;
		this.height = height;
		this.tree = tree;
	}

	@Override
	public Dimension getPreferredSize() {
		return new Dimension(this.width, this.height);
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		Graphics2D g2 = (Graphics2D) g;
		this.tree.countInOrder(); // run count in order to get the items
									// location in order

		// find the space between each section horizontally
		double xSpacing = (this.getWidth() * 0.99) / (this.tree.size());
		// find the space between each section vertically
		double ySpacing = ((this.getHeight()) * 0.99) / ((this.tree.height() + 1));

		InOrder itr = new InOrder(this.tree); // create a new iterator
		g2.setFont(new Font("Arial", Font.PLAIN, (int) Math.min(xSpacing, ySpacing) / 2)); // set up the font for the
																							// characters

		try {
			for (itr.first(); itr.isValid(); itr.advance()) {
				// print the character in it's position base upon it's x loc and
				// xspacing and y loc and yspacing
				g2.setColor(Color.RED);
				String string = "";// itr.current.element + " ";
				if (true)
					string += itr.current.rank;
				if (true) {
					if (itr.current.balance == Code.LEFT)
						string += " /";
					else if (itr.current.balance == Code.RIGHT)
						string += " \\";
					else if (itr.current.balance == Code.SAME)
						string += " =";
					else
						string += "shit";
				}

				g2.drawString(itr.current.element + "",
						(int) ((itr.current.xloc) * xSpacing + Math.min(xSpacing, ySpacing) * 2 / 6),
						(int) ((itr.current.yloc) * ySpacing + Math.min(xSpacing, ySpacing) * 3 / 7));
				g2.drawString(string, (int) ((itr.current.xloc) * xSpacing + Math.min(xSpacing, ySpacing) * 1 / 6),
						(int) ((itr.current.yloc) * ySpacing + Math.min(xSpacing, ySpacing) * 6 / 7));
				// draw the ellipse in it's position base upon it's x loc and
				// xspacing and y loc and yspacing
				Ellipse2D.Double e = new Ellipse2D.Double(((itr.current.xloc) * xSpacing),
						((itr.current.yloc) * ySpacing), Math.min(xSpacing, ySpacing), Math.min(xSpacing, ySpacing));
				g2.setColor(Color.black);
				g2.draw(e);
				g2.setColor(Color.blue);
				// if the node has a left child, draw a line to it. Don't worry
				// about whether the node has been drawn yet because it will be
				// eventually no matter what
				if (itr.current.getLeft() != null) {
					// calculate the center of the current and left child
					double x1 = ((itr.current.xloc) * xSpacing + Math.min(xSpacing, ySpacing) / 2);
					double y1 = ((itr.current.yloc) * ySpacing + Math.min(xSpacing, ySpacing) / 2);
					double x2 = ((itr.current.getLeft().xloc) * xSpacing + Math.min(xSpacing, ySpacing) / 2);
					double y2 = ((itr.current.getLeft().yloc) * ySpacing + Math.min(xSpacing, ySpacing) / 2);
					// calculate the slope of the line
					double theta = (y1 - y2) / (x1 - x2);
					// draw the line from the edge of one circle to the edge of the other using some trig
					Line2D.Double l = new Line2D.Double(x1 - Math.min(xSpacing, ySpacing) / 2
							* Math.cos(Math.atan(theta)), y1 - Math.min(xSpacing, ySpacing) / 2
							* Math.sin(Math.atan(theta)), x2 + Math.min(xSpacing, ySpacing) / 2
							* Math.cos(Math.atan(theta)), y2 + Math.min(xSpacing, ySpacing) / 2
							* Math.sin(Math.atan(theta)));
					g2.draw(l);

					// construct and draw the triangle based upon some mathematical relationships
					Polygon p = new Polygon();
					p.addPoint((int) (x2 + Math.min(xSpacing, ySpacing) / 2 * Math.cos(Math.atan(theta))),
							(int) (y2 + Math.min(xSpacing, ySpacing) / 2 * Math.sin(Math.atan(theta))));
					p.addPoint(
							(int) (x2 + Math.min(xSpacing, ySpacing) / 2 * Math.cos(Math.atan(theta)) * 1.5 + Math.min(
									xSpacing, ySpacing) / 8 * Math.cos(Math.atan(-1 / theta))),
							(int) (y2 + Math.min(xSpacing, ySpacing) / 2 * Math.sin(Math.atan(theta)) * 1.5 + Math.min(
									xSpacing, ySpacing) / 8 * Math.sin(Math.atan(-1 / theta))));
					p.addPoint(
							(int) (x2 + Math.min(xSpacing, ySpacing) / 2 * Math.cos(Math.atan(theta)) * 1.5 - Math.min(
									xSpacing, ySpacing) / 8 * Math.cos(Math.atan(-1 / theta))),
							(int) (y2 + Math.min(xSpacing, ySpacing) / 2 * Math.sin(Math.atan(theta)) * 1.5 - Math.min(
									xSpacing, ySpacing) / 8 * Math.sin(Math.atan(-1 / theta))));

					g2.fillPolygon(p);

				}
				if (itr.current.getRight() != null) {
					// calculate the center of the current and right child
					double x1 = ((itr.current.xloc) * xSpacing + Math.min(xSpacing, ySpacing) / 2);
					double y1 = ((itr.current.yloc) * ySpacing + Math.min(xSpacing, ySpacing) / 2);
					double x2 = ((itr.current.getRight().xloc) * xSpacing + Math.min(xSpacing, ySpacing) / 2);
					double y2 = ((itr.current.getRight().yloc) * ySpacing + Math.min(xSpacing, ySpacing) / 2);
					// calculate the slope of the line
					double theta = (y1 - y2) / (x1 - x2);
					// draw the line from the edge of one circle to the edge of the other using some trig
					Line2D.Double l = new Line2D.Double(x1 + Math.min(xSpacing, ySpacing) / 2
							* Math.cos(Math.atan(theta)), y1 + Math.min(xSpacing, ySpacing) / 2
							* Math.sin(Math.atan(theta)), x2 - Math.min(xSpacing, ySpacing) / 2
							* Math.cos(Math.atan(theta)), y2 - Math.min(xSpacing, ySpacing) / 2
							* Math.sin(Math.atan(theta)));
					g2.draw(l);

					// construct and draw the triangle based upon some mathematical relationships
					Polygon p = new Polygon();
					p.addPoint((int) (x2 - Math.min(xSpacing, ySpacing) / 2 * Math.cos(Math.atan(theta))),
							(int) (y2 - Math.min(xSpacing, ySpacing) / 2 * Math.sin(Math.atan(theta))));
					p.addPoint(
							(int) (x2 - Math.min(xSpacing, ySpacing) / 2 * Math.cos(Math.atan(theta)) * 1.5 + Math.min(
									xSpacing, ySpacing) / 8 * Math.cos(Math.atan(-1 / theta))),
							(int) (y2 - Math.min(xSpacing, ySpacing) / 2 * Math.sin(Math.atan(theta)) * 1.5 + Math.min(
									xSpacing, ySpacing) / 8 * Math.sin(Math.atan(-1 / theta))));
					p.addPoint(
							(int) (x2 - Math.min(xSpacing, ySpacing) / 2 * Math.cos(Math.atan(theta)) * 1.5 - Math.min(
									xSpacing, ySpacing) / 8 * Math.cos(Math.atan(-1 / theta))),
							(int) (y2 - Math.min(xSpacing, ySpacing) / 2 * Math.sin(Math.atan(theta)) * 1.5 - Math.min(
									xSpacing, ySpacing) / 8 * Math.sin(Math.atan(-1 / theta))));

					g2.fillPolygon(p);
				}

			}
		} catch (NoSuchElementException e) {
			// nothing
		}

	}
}
